package cn.gov.msa.util.encrypt;

import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.apache.commons.codec.binary.Base64;

import net.sf.json.JSONObject;



/**
 * 
* @ClassName: BizMsgCrypt 
* @Description: 加解密工具
* @Company:方正
* @author zhaolei 
* @version 1.0 2018年11月16日 下午1:44:37
 */
public class BizMsgCrypt {

	static Charset CHARSET = Charset.forName("utf-8");
	Base64 base64 = new Base64();
	byte[] aesKey;
	String appId;

	/**
	 * 构造函数
	 * @param encodingAesKey 秘钥-43位
	 * @param appId 集成应用appid
	 * 
	 */
	public BizMsgCrypt(String encodingAesKey, String appId){
		if (encodingAesKey.length() != 43) {
			throw new RuntimeException("encodingAesKey 非法");
		}
		this.appId = appId;
		aesKey = Base64.decodeBase64(encodingAesKey + "=");
	}

	/**
	 * 对明文进行加密.
	 * 
	 * @param text 需要加密的明文
	 * @return 加密后base64编码的字符串
	 */
	private String encrypt(String randomStr, String text){
		ByteGroup byteCollector = new ByteGroup();
		byte[] randomStrBytes = randomStr.getBytes(CHARSET);
		byte[] textBytes = text.getBytes(CHARSET);
		byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
		byte[] appidBytes = appId.getBytes(CHARSET);

		// randomStr + networkBytesOrder + text + appid
		byteCollector.addBytes(randomStrBytes);
		byteCollector.addBytes(networkBytesOrder);
		byteCollector.addBytes(textBytes);
		byteCollector.addBytes(appidBytes);

		// ... + pad: 使用自定义的填充方式对明文进行补位填充
		byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
		byteCollector.addBytes(padBytes);

		// 获得最终的字节流, 未加密
		byte[] unencrypted = byteCollector.toBytes();

		try {
			//1.构造密钥生成器，指定为AES算法,不区分大小写
            KeyGenerator keyGen=KeyGenerator.getInstance("AES");
            //2.根据规则初始化密钥生成器-兼容linux、windows
//            keyGen.init(128, new SecureRandom(aesKey));
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            random.setSeed(aesKey);
            //生成一个128位的随机源,根据传入的字节数组
            keyGen.init(128, random);
              //3.产生原始对称密钥
            SecretKey originalKey=keyGen.generateKey();
              //4.获得原始对称密钥的字节数组
            byte [] raw=originalKey.getEncoded();
            //5.根据字节数组生成AES密钥
            SecretKey key=new SecretKeySpec(raw, "AES");
              //6.根据指定算法AES自成密码器
            Cipher cipher=Cipher.getInstance("AES");
              //7.初始化密码器，第一个参数为加密(ENCRYPT_MODE)或者解密解密(DECRYPT_MODE)操作，第二个参数为使用的KEY
            cipher.init(Cipher.ENCRYPT_MODE, key);

			// 8.加密
			byte[] encrypted = cipher.doFinal(unencrypted);

			// 使用BASE64对加密后的字符串进行编码
			String base64Encrypted = base64.encodeToString(encrypted);
//			String base64Encrypted = parseByte2HexStr(encrypted);
			return base64Encrypted;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 *   数据加密打包.
	 * <ol>
	 * 	<li>对数据进行AES-CBC加密</li>
	 * 	<li>生成安全签名</li>
	 * 	<li>将数据密文和安全签名打包成json格式</li>
	 * </ol>
	 * 
	 * @param replyMsg 要加密的数据，json格式的字符串
	 * @param timeStamp 时间戳，可自己生成 
	 * @param nonce 随机串，可自己生成
	 * 
	 * @return 包含加密后密文的json，包括signature, timestamp, nonce, encrypt的json格式的字符串
	 */
	public JSONObject encryptMsg(String replyMsg, String timeStamp, String nonce){
		// 加密
		String encrypt = encrypt(getRandomStr(), replyMsg);

		// 生成安全签名
		if (timeStamp == ""||timeStamp == null) {
			timeStamp = Long.toString(System.currentTimeMillis());
		}

		String signature = SHA1.getSHA1(timeStamp, nonce, encrypt);

		//生成发送的json串
		JSONObject json=new JSONObject();
		json.put("encrypt", encrypt);//加密数据
		json.put("signature", signature);//安全签名
		json.put("timestamp", timeStamp);//时间戳
		json.put("nonce", nonce);//随机字符串
		
		return json;
	}
	/**
	 * 对密文进行解密.
	 * 
	 * @param text 需要解密的密文
	 * @return 解密得到的明文 appid 应用id和加密内容
	 */
	private JSONObject decrypt(String text){
		byte[] original;
		try {
			 //1.构造密钥生成器，指定为AES算法,不区分大小写
            KeyGenerator keyGen=KeyGenerator.getInstance("AES");
            //2.根据规则初始化密钥生成器-兼容linux、windows
//            keyGen.init(128, new SecureRandom(aesKey));
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            random.setSeed(aesKey);
            //生成一个128位的随机源,根据传入的字节数组
            keyGen.init(128, random);
              //3.产生原始对称密钥
            SecretKey originalKey=keyGen.generateKey();
              //4.获得原始对称密钥的字节数组
            byte [] raw=originalKey.getEncoded();
            //5.根据字节数组生成AES密钥
            SecretKey key=new SecretKeySpec(raw, "AES");
              //6.根据指定算法AES自成密码器
            Cipher cipher=Cipher.getInstance("AES");
              //7.初始化密码器，第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作，第二个参数为使用的KEY
            cipher.init(Cipher.DECRYPT_MODE, key);

			// 8.使用BASE64对密文进行解码
			byte[] encrypted = Base64.decodeBase64(text);
//			byte[] encrypted = parseHexStr2Byte(text); 

			// 解密
			original = cipher.doFinal(encrypted);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		String content, appId;
		try {
			// 去除补位字符
			byte[] bytes = PKCS7Encoder.decode(original);

			// 分离16位随机字符串,网络字节序和AppId
			byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);

			int xmlLength = recoverNetworkBytesOrder(networkOrder);
			//获取解密后的内容
			content = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
			//获取appid
			appId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
					CHARSET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		System.err.println("content=> "+content);
		System.err.println("appid=> "+appId);
		
		JSONObject json=new JSONObject();
		json.put("appId", appId);//获取到的应用id,拿着此id去查询是否存在
		json.put("data", JSONObject.fromObject(content));//加密内容
		return json;

	}
	/**
	 * 检验数据的真实性，并且获取解密后的明文.
	 * <ol>
	 * 	<li>利用收到的密文生成安全签名，进行签名验证</li>
	 * 	<li>若验证通过，则提取json中的加密数据</li>
	 * 	<li>对数据进行解密</li>
	 * </ol>
	 * 
	 * @param msgSignature 签名串，对应请求参数的signature
	 * @param timeStamp 时间戳，对应请求参数的timestamp
	 * @param nonce 随机串，对应请求参数的nonce
	 * @param encrypt 密文，
	 * 
	 * @return 解密后的原文
	 */
	public JSONObject decryptMsg(String msgSignature, String timeStamp, String nonce, String encrypt)
	 {
		long currentTime = System.currentTimeMillis();
		// 对比时间戳 超过1小时校验失败
        long time = Long.parseLong(timeStamp);
        
        if(currentTime-time>=60*60*1000){
        	
        	throw new RuntimeException("时间戳间隔超过1小时");
        }
		// 验证安全签名
		String signature = SHA1.getSHA1(timeStamp, nonce, encrypt);
		// 验证签名比较是否相等
		 System.err.println("第三方收到请求中的签名：" + msgSignature);
		 System.err.println("第三方校验签名：" + signature);
		
		if (!signature.equals(msgSignature)) {
			throw new RuntimeException("签名验证错误");
		}

		// 解密
		JSONObject result = decrypt(encrypt);
		return result;
	}
	
	
	// 生成4个字节的网络字节序
	byte[] getNetworkBytesOrder(int sourceNumber) {
		byte[] orderBytes = new byte[4];
		orderBytes[3] = (byte) (sourceNumber & 0xFF);
		orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
		orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
		orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
		return orderBytes;
	}

	// 还原4个字节的网络字节序
	int recoverNetworkBytesOrder(byte[] orderBytes) {
		int sourceNumber = 0;
		for (int i = 0; i < 4; i++) {
			sourceNumber <<= 8;
			sourceNumber |= orderBytes[i] & 0xff;
		}
		return sourceNumber;
	}

	// 随机生成16位字符串
	public static String getRandomStr() {
		String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < 16; i++) {
			int number = random.nextInt(base.length());
			sb.append(base.charAt(number));
		}
		return sb.toString();
	}
	
	/**将二进制转换成16进制 
	 * @param buf 
	 * @return 
	 */  
	public static String parseByte2HexStr(byte buf[]) {  
	        StringBuffer sb = new StringBuffer();  
	        for (int i = 0; i < buf.length; i++) {  
	                String hex = Integer.toHexString(buf[i] & 0xFF);  
	                if (hex.length() == 1) {  
	                        hex = '0' + hex;  
	                }  
	                sb.append(hex.toUpperCase());  
	        }  
	        return sb.toString();  
	} 
	/**将16进制转换为二进制 
	 * @param hexStr 
	 * @return 
	 */  
	public static byte[] parseHexStr2Byte(String hexStr) {  
	        if (hexStr.length() < 1)  
	                return null;  
	        byte[] result = new byte[hexStr.length()/2];  
	        for (int i = 0;i< hexStr.length()/2; i++) {  
	                int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);  
	                int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);  
	                result[i] = (byte) (high * 16 + low);  
	        }  
	        return result;  
	} 
}
